﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Text;

using Tradex;
using Tradex.ComponentModel;
using Tradex.Market;
using Tradex.Messages;
using Tradex.Trading;

using Tradex.Connectivity.ConnectorModel;
using Tradex.Connectivity.DataLog;

namespace Tradex.Connectivity {

    /// <summary>
    /// The ConnectorHost is the class called by the Wrappers to do the actual work of hosting a connector. It has only one static method that
    /// does all the initialization work. It will locate the ConnectorName - if possible - using the name, initialize it and hook up (if possible)
    /// with a control host. It will then forward operations in both directions as needed.
    /// </summary>
    public sealed class ConnectorHost :
        IConnectorCallback,
        ITradingConnectorCallback,
        IMarketConnectorCallback,

        IDisposable
    {

        /// <summary>
        /// Creates a new ConnectorHost for a given connector.
        /// </summary>
        /// <param name="connectorName"></param>
        /// <param name="connector"></param>
        public ConnectorHost(string connectorName, IConnector connector) {
            if (string.IsNullOrWhiteSpace(connectorName)) {
                throw new ArgumentException("value can not be empty", "connectorName");
            }
            if (connector == null) {
                throw new ArgumentException("value can not be null", "connector");
            }
            Contract.EndContractBlock();

            ConnectorName = connectorName;

            _Connector = connector;
            _Connector.SetCallback(this);

            TradingConnector = _Connector as ITradingConnector;
            if (TradingConnector != null) {
                TradingConnector.SetCallback(this);
            }

            MarketConnector = _Connector as IMarketConnector;
            if (MarketConnector != null) {
                MarketConnector.SetCallback(this);
            }
        }

        internal string ConnectorName { get; private set; }

        internal IConnectorHostTarget HostTarget { get; set; }

        /// <summary>
        /// This is the base interface pointer for the connector. Other interface pointers are added as exposed
        /// by the connector.
        /// </summary>
        IConnector _Connector;

        /// <summary>
        /// This is the interface for connecting to a broker. This interface provides account information
        /// (existing accounts, position information) as well as allows order management (submission, changes,
        /// cancel etc.).
        /// </summary>
        internal ITradingConnector TradingConnector { get; private set; }

        internal IMarketConnector MarketConnector { get; private set; }

        internal void ProcessMessage(Message message) {
            if (message == null) {
                throw new ArgumentException("value can not be null", "message");
            }
            Contract.EndContractBlock();
            switch (message.Code) {
                case MessageCode.InstrumentActivityRequest:
                    ProcessActivityRequest(message);
                    break;
                case MessageCode.InstrumentRequest:
                    ProcessInstrumentRequest(message);
                    break;
                case MessageCode.AccountsRequest:
                    TradingConnector.GetAccounts();
                    break;
            }
        }

        void ProcessActivityRequest(Message message) {
            InstrumentActivityRequest request = (InstrumentActivityRequest)message;
            MarketConnector.Subscribe(request.InstrumentKey, request.ActivityFilter);
        }

        void ProcessInstrumentRequest(Message message) {
            InstrumentRequest request = (InstrumentRequest)message;
            MarketConnector.GetReferenceData(request.InstrumentKey);
        }

        /// <inheritdoc />
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Tradex.ConnectionStatus.#ctor(System.DateTime,Tradex.ConnectionStatusCode,System.String)")]
        public void Connect(string connectionString) {
            if (_Connector.IsConnected) {
                return;
            }
            HostTarget.SetConnectionStatus(new ConnectionStatus(DateTime.UtcNow, ConnectionStatusCode.Connecting, "ConnectorName is connecting"));
            _Connector.Connect(connectionString);
        }

        /// <inheritdoc />
        public void Disconnect() {
            if (!_Connector.IsConnected) {
                return;
            }
            _Connector.Disconnect();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Tradex.ConnectionStatus.#ctor(System.DateTime,Tradex.ConnectionStatusCode,System.String)")]
        internal void Shutdown() {
            Disconnect();
            HostTarget.SetConnectionStatus(new ConnectionStatus(DateTime.UtcNow, ConnectionStatusCode.Stopped, "ConnectorName has stopped"));
        }

        #region "IConnectorCallback"

        /// <inheritdoc />
        void IConnectorCallback.LogEntry(TraceLevel traceLevel, string subject, string message, string origin, string reference) {
            LogEntryCreateMessage logEntry = new LogEntryCreateMessage(
                DateTime.UtcNow,
                traceLevel,
                subject,
                message,
                origin,
                reference
            );
            HostTarget.SubmitMessage(logEntry);
        }

        /// <inheritdoc />
        void IConnectorCallback.StatusUpdate(ConnectionStatusCode status, string message) {
            ConnectionStatus connectionStatus = new ConnectionStatus(DateTime.UtcNow, status, message);
            HostTarget.SetConnectionStatus(connectionStatus);
        }

        #endregion "IConnectorCallback"

        #region "ITradingConnectorCallback"

        void ITradingConnectorCallback.AccountsUpdate(AccountKey[] accounts) {
            AccountsUpdateMessage accountsUpdate = new AccountsUpdateMessage(
                null, accounts
            );
            HostTarget.SubmitMessage(accountsUpdate);
        }

        void ITradingConnectorCallback.AccountUpdate(AccountKey account, AccountUpdate update, PositionUpdate position) {
        }

        void ITradingConnectorCallback.OrderUpdate(AccountKey account, OrderKey order, OrderId connectionKey, OrderUpdate update, OrderExecution execution) {
            OrderUpdateMessage orderUpdate = new OrderUpdateMessage(order, connectionKey, update, execution);
            HostTarget.SubmitMessage(orderUpdate);
        }

        void ITradingConnectorCallback.OrdersUpdate(AccountKey accountKey, OrderUpdate[] orders) {
        }

        #endregion "ITradingConnectorCallback"

        #region "IMarketConnectorCallback"

        void IMarketConnectorCallback.Activity(InstrumentKey instrumentKey, Activity update) {
            InstrumentActivityUpdateMessage mui = new InstrumentActivityUpdateMessage(instrumentKey, update);
            HostTarget.SubmitMessage(mui);
        }

        void IMarketConnectorCallback.InstrumentUpdate(InstrumentKey instrumentKey, InstrumentInfo instrumentInfo, InstrumentPricing instrumentPricing) {
            InstrumentUpdateMessage instrumentUpdate = new InstrumentUpdateMessage(instrumentKey, instrumentInfo, instrumentPricing);
            HostTarget.SubmitMessage(instrumentUpdate);

        }

        #endregion "IMarketConnectorCallback"

        /// <inheritdoc />
        public void Dispose() {
            Dispose(true);
        }

        private void Dispose(bool disposing) {
            if (disposing) {
                if (_Connector != null) {
                    _Connector.Dispose();
                    _Connector = null;
                }
                if (TradingConnector != null) {
                    TradingConnector = null;
                }
                if (MarketConnector != null) {
                    MarketConnector = null;
                }
            }
        }

        /// <summary>
        /// The HostMain method is the main method called by the different ConnectorHost executables to do their work.
        /// This keeps the implementation of the executables very small (just fowarding the parameters here).
        /// </summary>
        /// <param name="args"></param>
        public static void HostMain (string [] args) {
            ConnectorHostParameters parameters = ConnectorHostParameters.Parse (args);
            ConnectorHost host = ConnectorHost.InitializeConnector(parameters.Connector, parameters.Key);
            ConnectorApi api = null;
            ConnectorApiHost apiHost = null;

            IConnectorUserInterface connectorInterface = null;

            try {
                // Interface or not?
                if (parameters.Interface == true) {
                    connectorInterface = (IConnectorUserInterface)Activator.CreateInstance(
                        "Tradex.Connectivity.UserInterface",
                        "Tradex.Connectivity.UserInterface.UserInterfaceController"
                    ).Unwrap();
                    connectorInterface.Start();
                }

                api = new ConnectorApi(host, parameters.Key);
                apiHost = new ConnectorApiHost(api, parameters.Key);

                connectorInterface.Connect(api, apiHost);

                // Finally, if we got a connection string, then... we actually do queue a connection event..
                if ((!String.IsNullOrWhiteSpace(parameters.Connection))) {
                }

                // Waiting loop. This (main) thread now serves as a watchdog and loops
                // every second.
                while (api.Status.Code != ConnectionStatusCode.Stopped) {
                    api.TimerAction();
                    Thread.Sleep(1000);
                }
            } finally {
                if (apiHost != null) {
                    apiHost.Dispose();
                }
                if (host != null) {
                    host.Dispose();
                }
                if (api != null) {
                    api.Dispose();
                }
            }
        }

        /// <summary>
        /// The initialization method initializes the ConnectorrHost and starts a specific connector.
        /// </summary>
        /// <param name="connectorName"></param>
        /// <param name="connectorInstanceKey"></param>
        /// <returns></returns>
        public static ConnectorHost InitializeConnector(string connectorName, string connectorInstanceKey) {
            if (string.IsNullOrEmpty(connectorName)) {
                throw new ArgumentException("value can not be null", "connectorName");
            }
            if (string.IsNullOrEmpty(connectorInstanceKey)) {
                throw new ArgumentException("value can not be null", "connectorInstanceKey");
            }
            Contract.EndContractBlock();

            // ConnectorName assembly. i.e. Tradex.Connectivity.Rithmic for the "Rithmic" connector.
            string conAssembly = String.Format(CultureInfo.InvariantCulture, "Tradex.Connectivity.{0}", connectorName);
            // ConnectorName class, i.e. Tradex.Connectivity.Rithmic.RithmicConnector for the "Rithmic" connector.
            string conClass = String.Format(CultureInfo.InvariantCulture, "Tradex.Connectivity.{0}.{0}Connector", connectorName);

            IConnector connector = (IConnector)Activator.CreateInstance(conAssembly, conClass).Unwrap();
            return new ConnectorHost(connectorInstanceKey, connector);
        }

        /// <summary>
        /// Starts an external ConnectorHost (Tradex.Connectivity.ConnectorHostxx.exe) of the appropriate type
        /// (as selected by connectorHostType) and passes in the parameters via the command line.
        /// </summary>
        /// <param name="connectorName"></param>
        /// <param name="connectorInstanceKey"></param>
        /// <param name="connectorHostType"></param>
        public static void StartConnector(string connectorName, string connectorInstanceKey, ConnectionHostType connectorHostType) {
            if (string.IsNullOrWhiteSpace(connectorName)) {
                throw new ArgumentException("value can not be null", "connectorName");
            }
            if (string.IsNullOrWhiteSpace(connectorInstanceKey)) {
                throw new ArgumentException("value can not be null", "connectorInstanceKey");
            }
            Contract.EndContractBlock();

            ProcessStartInfo psi = new ProcessStartInfo();
            switch (connectorHostType) {
                case ConnectionHostType.X32: {
                    psi.FileName = "Tradex.Connectivity.Wrapper32.exe";
                    break;
                }
                case ConnectionHostType.X64: {
                    psi.FileName = "Tradex.Connectivity.Wrapper64.exe";
                    break;
                }
            }
            psi.Arguments = String.Format (CultureInfo.InvariantCulture, 
                "{0} {1}",
                connectorName, connectorInstanceKey
            );
            psi.WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory;
            Process.Start (psi);
        }

    }

}
